package com.tony.demo.ui.performance.image;

import android.app.ActivityManager;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Build;
import android.os.Environment;
import android.text.TextUtils;
import android.util.LruCache;

import com.puhui.lib.utils.DMLog;
import com.tony.demo.BuildConfig;
import com.tony.demo.ui.performance.image.disk.DiskLruCache;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.WeakReference;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

//import com.bumptech.glide.disklrucache.DiskLruCache;  //原理一样，里面代码都几乎很少区别

public class ImageCacheCopy {
    private static ImageCacheCopy instance = null;
    private WeakReference<Context> mContext;
    private LruCache<String, Bitmap> memoryCache;
    private DiskLruCache diskLruCache;
    private BitmapFactory.Options options = new BitmapFactory.Options();
    /**
     * 在未使用复用的情况下，每张图片都需要一块内存。而使用复用的时候，如果存在能被复用的图片会重复使用该图片的内存。
     * 所以复用并不能减少程序正在使用的内存大小。Bitmap复用，解决的是减少频繁申请内存带来的性能(抖动、碎片)问题。
     * <p>
     * Bitmap所占用的内存大小跟分辨率，尺寸宽高，图片格式有关
     */
    private Set<WeakReference<Bitmap>> reusablePool;
    private ReferenceQueue<Bitmap> referenceQueue;  //引用队列

    private ImageCacheCopy(Context context, String cachePath) {
        init(context, cachePath);
    }

    public static ImageCacheCopy getInstance(Context context, String cachePath) {
        if (instance == null) {
            synchronized (ImageCacheCopy.class) {
                if (instance == null) {
                    instance = new ImageCacheCopy(context, cachePath);
                }
            }
        }
        return instance;
    }

    private void init(Context context, String cachePath) {
        this.mContext = new WeakReference<>(context.getApplicationContext());
        ActivityManager am = (ActivityManager) mContext.get().getSystemService(Context.ACTIVITY_SERVICE);
        if (null == am) {
            return;
        }
        int memorySize = am.getMemoryClass();  //应用进程可用内存 单位M
        memoryCache = new MyLruCache<String, Bitmap>(memorySize / 8 * 1024 * 1024);  //单位  字节
        reusablePool = Collections.synchronizedSet(new HashSet<WeakReference<Bitmap>>());
        initReferenceQueue();
        initDiskCache(cachePath);
    }

    /**
     * 初始化磁盘缓存
     *
     * @param cachePath 缓存路径
     */
    private void initDiskCache(String cachePath) {
        //context.getCacheDir().getAbsolutePath()  data/data/<application package>/cache
        //context.getFilesDir().getAbsolutePath()  data/data/<application package>/files
        //且不需要存储权限  卸载后数据跟着被删除
        /**
         * 应用程序在运行的过程中如果需要向手机上保存数据，一般是把数据保存在SDcard中的。
         * 大部分应用是直接在SDCard的根目录下创建一个文件夹，然后把数据保存在该文件夹中。
         * 这样当该应用被卸载后，这些数据还保留在SDCard中，留下了垃圾数据。
         * 如果你想让你的应用被卸载后，与该应用相关的数据也清除掉，该怎么办呢？
         * 通过Context.getExternalFilesDir()方法可以获取到 SDCard/Android/data/你的应用的包名/files/ 目录，一般放一些长时间保存的数据
         * 通过Context.getExternalCacheDir()方法可以获取到 SDCard/Android/data/你的应用包名/cache/目录，一般存放临时缓存数据
         * 如果使用上面的方法，当你的应用在被用户卸载后，SDCard/Android/data/你的应用的包名/ 这个目录下的所有文件都会被删除，不会留下垃圾信息。
         * 且不需要存储权限
         */
        if (TextUtils.isEmpty(cachePath)) {
            cachePath = mContext.get().getFilesDir().getAbsolutePath();
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                File file = mContext.get().getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS);
                if (null != file) {
                    cachePath = file.getAbsolutePath();
                }
            }
        }

        try {
            diskLruCache = DiskLruCache.open(new File(cachePath), BuildConfig.VERSION_CODE, 1, 20 * 1024 * 1024);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 初始化引用队列
     */
    private void initReferenceQueue() {
        if (null == referenceQueue) {
            //当弱引用需要被回收的时候，会进到这个队列中
            referenceQueue = new ReferenceQueue<>();
            //单开一个线程，去扫描引用队列中GC扫描到的内容，交到native层去释放
            //这里开个线程，无限循环去从引用对列中remove数据，是为了通过主动调用bitmap.recycle()方法来加快bitmap的回收。
            //因为Android GC是通过两次扫描来回收对象的，这里第一次扫描后标记会回收状态后会放入这个引用队列，在对二次扫描时才会真正回收。
            Thread clearReferenceQueue = new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true) {  //true  可以通过一个变量来控制开关
                        try {
                            //remove()是阻塞式的
                            Bitmap bitmap = referenceQueue.remove().get();
                            if (null != bitmap && !bitmap.isRecycled()) {
                                bitmap.recycle();
                                DMLog.e("referenceQueue", "被GC扫描到，主动释放内存空间");
                            }
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            });
            clearReferenceQueue.start();
        }
    }

    private class MyLruCache<K, V> extends LruCache<String, Bitmap> {
        MyLruCache(int maxSize) {
            super(maxSize);
        }

        /**
         * @return value占用的内存大小
         */
        @Override
        protected int sizeOf(String key, Bitmap value) {
            //19之前 被复用的Bitmap与使用复用的Bitmap必须宽、高相等并且使用复用的Bitmap解码时设置的inSampleSize为1，才允许复用
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                return value.getAllocationByteCount(); //得到bitmap占用的实际内存大小
            }
            //getByteCount()在19之前，得到的就是bitmap占用的实际内存大小；在19之后得到的bitmap本身的内存大小
            return value.getByteCount();
        }

        /**
         * 当lru满了bitmap从lru中移除对象时会调用
         */
        @Override
        protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) {
            if (oldValue.isMutable()) {
                //将从lru中移除来的oldValue添加到复用池中，当GC扫描到这块内存时，会将其添加到引用队列中
                //oldValue 回收策略  3.0之前在native层回收， 3.0 ~ 8.0之前，在Java层通过GC回收， 8.0之后回到native层回收
                //把这些图片放到一个复用沲中
                reusablePool.add(new WeakReference<Bitmap>(oldValue, referenceQueue));
                DMLog.e(this.getClass().getCanonicalName(), "从LruCache中移除  复用池大小为：" + reusablePool.size());
            } else {
                //oldValue就是移出来的对象
                oldValue.recycle();
            }
        }
    }

    public void putBitmapToMemory(String key, Bitmap bitmap) {
        memoryCache.put(key, bitmap);
    }

    public Bitmap getBitmapFromMemory(String key) {
        return memoryCache.get(key);
    }

    public void clearMemoryCache() {
        memoryCache.evictAll();
    }

    /**
     * 获取可复用的bitmap, 这里得到的不是图片，而是可复用的Bitmap内存块
     */
    public Bitmap getReusableBitmap(int w, int h, int inSampleSize) {
//        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {  //api 3.0
//            return null;
//        }

        Bitmap reusable = null;
        Iterator<WeakReference<Bitmap>> iterator = reusablePool.iterator();
        while (iterator.hasNext()) {
            Bitmap bitmap = iterator.next().get();
            if (null != bitmap) {
                //可以复用
                if (checkInBitmap(bitmap, w, h, inSampleSize)) {
                    reusable = bitmap;
                    iterator.remove();
                    DMLog.e(this.getClass().getCanonicalName(), "可以复用 可以复用");
                    break;
                } else {
                    iterator.remove();
                }
            }
        }
        return reusable;
    }

    private boolean checkInBitmap(Bitmap bitmap, int w, int h, int inSampleSize) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
            return bitmap.getWidth() == w && bitmap.getHeight() == h && inSampleSize == 1;
        }

        if (inSampleSize >= 1) {
            w /= inSampleSize;
            h /= inSampleSize;
        }

        int byteCount = w * h * getPixelsCount(bitmap.getConfig());
        return byteCount <= bitmap.getAllocationByteCount();
    }

    /**
     * Bitmap.Config ARGB_4444：每个像素占四位，即A=4，R=4，G=4，B=4，那么一个像素点占4+4+4+4=16位 
     * Bitmap.Config ARGB_8888：每个像素占四位，即A=8，R=8，G=8，B=8，那么一个像素点占8+8+8+8=32位
     * Bitmap.Config RGB_565：每个像素占四位，即R=5，G=6，B=5，没有透明度，那么一个像素点占5+6+5=16位
     * Bitmap.Config ALPHA_8：每个像素占四位，只有透明度，没有颜色。
     */
    private static int getPixelsCount(Bitmap.Config config) {
        if (config == Bitmap.Config.ARGB_8888) {
            return 4;
        } else if (config == Bitmap.Config.RGB_565) {
            return 2;
        } else if (config == Bitmap.Config.ARGB_4444) {
            return 2;
        } else if (Build.VERSION.SDK_INT > Build.VERSION_CODES.O && config == Bitmap.Config.RGBA_F16) {
            //每个像素存储在8个字节上
            return 8;
        } else if (config == Bitmap.Config.ALPHA_8) {
            return 1;
        } else {
            return 2;
        }
    }

    /**
     * 从磁盘缓存中获取图片
     *
     * @param key
     * @param reusableBitmap 可复用的内存
     */
    public Bitmap getBitmapFromDisk(String key, Bitmap reusableBitmap) {
        DiskLruCache.Snapshot snapshot = null;
        Bitmap bitmap = null;
        InputStream inputStream = null;
        //这里的提示信息是： 'try' can use automatic resource management
        //意识是说实现了AutoCloseAble接口的数据流，可以不用在finally代码块里关闭了，系统会自动关闭
        //Snapshot和InputStream都实现了该接口
        try {
            snapshot = diskLruCache.get(key);
            if (null != snapshot) {
                inputStream = snapshot.getInputStream(0);
                options.inMutable = true;
                options.inBitmap = reusableBitmap;
                bitmap = BitmapFactory.decodeStream(inputStream, null, options);
                if (null != bitmap) {
                    memoryCache.put(key, bitmap);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != snapshot) {
                snapshot.close();
            }

            if (null != inputStream) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return bitmap;
    }

    /**
     * 将获取到的bitMap存入磁盘缓存
     */
    public void putIntoDiskCache(String key, Bitmap bitmap) {
        DiskLruCache.Snapshot snapshot = null;
        OutputStream outputStream = null;
        try {
            snapshot = diskLruCache.get(key);
            //弱缓存中没有这个文件则进行缓存
            if (null == snapshot) {
                DiskLruCache.Editor editor = diskLruCache.edit(key);
                if (null != editor) {
                    outputStream = editor.newOutputStream(0);
                    bitmap.compress(Bitmap.CompressFormat.JPEG, 50, outputStream);
                    editor.commit();
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != snapshot) {
                snapshot.close();
            }

            if (null != outputStream) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}









